package chapter8
{
	import com.adobe.utils.*;
	
	import flash.display.*;
	import flash.display.Bitmap;
	import flash.display.Stage;
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.events.*;
	import flash.events.Event;
	import flash.geom.*;
	import flash.text.*;
	import flash.utils.*;
	
	import utils.DisplayMolehill;
	import utils.Game_input;
	import utils.Game_particlesystem;
	import utils.Game_timer;
	import utils.IRender;
	import utils.Molehill_entity;
	import utils.Molehill_particle;
	
	public class Chapter8Demp extends DisplayMolehill implements IRender
	{
		// handles all timers for us
		private var gametimer:Game_timer;
		// handles keyboard and mouse inputs
		private var gameinput:Game_input;
		
		// all known entities in the world
		private var chase_camera:Molehill_entity
		private var player:Molehill_entity;
		private var props:Vector.<Molehill_entity>;
		private var enemies:Vector.<Molehill_entity>;
		private var bullets:Vector.<Molehill_entity>;
		private var particles:Vector.<Molehill_entity>;
		
		// reusable entity pointer (for speed and to avoid GC)
		private var entity:Molehill_entity;
		// we want to remember these for use in game_step()
		private var asteroids1:Molehill_entity;
		private var asteroids2:Molehill_entity;
		private var asteroids3:Molehill_entity;
		private var asteroids4:Molehill_entity;
		private var engine_glow:Molehill_entity;
		private var sky:Molehill_entity;
		
		// used by game_step()
		private const move_speed:Number = 1.0; // units per ms
		private const asteroid_rotation_speed:Number = 0.001; // deg per ms
		
		// used by the GUI
		private var fps_last:uint = getTimer();
		private var fps_ticks:uint = 0;
		private var fps_tf:TextField;
		private var score_tf:TextField;
		private var score:uint = 0;
		
		// the 3d graphics window on the stage
		private var context3D:Context3D;
		// the compiled shader used to render our meshes
		private var shaderProgram1:Program3D;
		
		// matrices that affect the mesh location and camera angles
		private var projectionmatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D();
		private var viewmatrix:Matrix3D = new Matrix3D();
		
		
		// used for our particle system demo
		private var next_shoot_time:uint = 0;
		private var shoot_delay:uint = 0;
		private var explo:Molehill_particle;
		private var particle_system:Game_particlesystem;
		private var scene_polycount:uint = 0;
		
		// explosion start - 336 polygons
		[Embed (source = "assets/art/explosion1.obj",mimeType = "application/octet-stream")]
		private var explosion1_data:Class;
		// explosion end - 336 polygons
		[Embed (source = "assets/art/explosion2.obj",mimeType = "application/octet-stream")]
		private var explosion2_data:Class;
		
		/* TEXTURES: Pure AS3 and Flex version:
		* if you are using Adobe Flash CS5
		* comment out the following: */
		[Embed (source = "assets/art/spaceship_texture.jpg")]
		private var playerTextureBitmap:Class;
		private var playerTextureData:Bitmap = new playerTextureBitmap();
		[Embed (source = "assets/art/terrain_texture.jpg")]
		private var terrainTextureBitmap:Class;
		private var terrainTextureData:Bitmap = new terrainTextureBitmap();
		[Embed (source = "assets/art/craters.jpg")]
		private var cratersTextureBitmap:Class;
		private var cratersTextureData:Bitmap = new cratersTextureBitmap();
		[Embed (source = "assets/art/sky.jpg")]
		private var skyTextureBitmap:Class;
		private var skyTextureData:Bitmap = new skyTextureBitmap();
		[Embed (source = "assets/art/engine.jpg")]
		private var puffTextureBitmap:Class;
		private var puffTextureData:Bitmap = new puffTextureBitmap();
		[Embed (source = "assets/art/hud_overlay.png")]
		private var hud_overlay_data:Class;
		
		[Embed (source = "assets/art/shockwave.png")]
		private var testBitmap:Class;
		private var testTextureData:Bitmap = new testBitmap();
		
		// The Molehill Textures that use the above
		private var playerTexture:Texture;
		private var terrainTexture:Texture;
		private var cratersTexture:Texture;
		private var skyTexture:Texture;
		private var puffTexture:Texture;
		private var testTexture:Texture;
		
		// the player
		[Embed (source = "assets/art/spaceship.obj",mimeType = "application/octet-stream")]
		private var my_obj_data5:Class;
		// the engine glow
		[Embed (source = "assets/art/puff.obj",mimeType = "application/octet-stream")]
		private var puff_obj_data:Class;
		// The terrain mesh data
		[Embed (source = "assets/art/terrain.obj",mimeType = "application/octet-stream")]
		private var terrain_obj_data:Class;
		// an asteroid field
		[Embed (source = "assets/art/asteroids.obj",mimeType = "application/octet-stream")]
		private var asteroids_obj_data:Class;
		// the sky
		[Embed (source = "assets/art/sphere.obj",mimeType = "application/octet-stream")]
		private var sky_obj_data:Class;
		
		
		public function Chapter8Demp(stage:Stage)
		{
			super(stage);
			
			// start the game timer
			gametimer = new Game_timer(heartbeat);
			gameinput = new Game_input(stage);
			
			// create some empty arrays
			props = new Vector.<Molehill_entity>();
			enemies = new Vector.<Molehill_entity>();
			bullets = new Vector.<Molehill_entity>();
			particles = new Vector.<Molehill_entity>();
			
			// add some text labels
			initGUI();
			// and request a context3D from Molehill
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}
		
		private function updateScore():void
		{
			// for now, you earn points over time
			score++;
			// padded with zeroes
			if (score < 10) score_tf.text = 'Score: 00000' + score;
			else if (score < 100) score_tf.text = 'Score: 0000' + score;
			else if (score < 1000) score_tf.text = 'Score: 000' + score;
			else if (score < 10000) score_tf.text = 'Score: 00' + score;
			else if (score < 100000) score_tf.text = 'Score: 0' + score;
			else score_tf.text = 'Score: ' + score;
		}
		
		public function uploadTextureWithMipmaps(dest:Texture, src:BitmapData):void
		{
			var ws:int = src.width;
			var hs:int = src.height;
			var level:int = 0;
			var tmp:BitmapData;
			var transform:Matrix = new Matrix();
			//var tmp2:BitmapData;
			
			tmp = new BitmapData( src.width, src.height, true, 0x00000000);
			
			while ( ws >= 1 && hs >= 1 )
			{                                
				tmp.draw(src, transform, null, null, null, true);    
				dest.uploadFromBitmapData(tmp, level);
				transform.scale(0.5, 0.5);
				level++;
				ws >>= 1;
				hs >>= 1;
				if (hs && ws) 
				{
					tmp.dispose();
					tmp = new BitmapData(ws, hs, true, 0x00000000);
				}
			}
			tmp.dispose();
		}
		
		private function initGUI():void
		{
			// heads-up-display overlay
			//addChild(hud_overlay);
			// a text format descriptor used by all gui labels
			var myFormat:TextFormat = new TextFormat();
			myFormat.color = 0xFFFFAA;
			myFormat.size = 16;
			// create an FPSCounter that displays the framerate on screen
			fps_tf = new TextField();
			fps_tf.x = 4;
			fps_tf.y = 0;
			fps_tf.selectable = false;
			fps_tf.autoSize = TextFieldAutoSize.LEFT;
			fps_tf.defaultTextFormat = myFormat;
			fps_tf.text = "Initializing Molehill...";
			addChild(fps_tf);
			// create a score display
			score_tf = new TextField();
			score_tf.x = 540;
			score_tf.y = 0;
			score_tf.selectable = false;
			score_tf.autoSize = TextFieldAutoSize.LEFT;
			score_tf.defaultTextFormat = myFormat;
			addChild(score_tf);
		}
		
		private function removeGUI(event:Event):void{
			/*if(hud_overlay.parent)
				stage.removeChild(hud_overlay)*/
		}
		
		private function addChild(displayObject:DisplayObject):void{
			stage.addChild(displayObject);
		}
		
		private function initShaders():void
		{
			// A simple vertex shader which does a 3D transformation
			// for simplicity, it is used by all four shaders
			var vertexShaderAssembler:AGALMiniAssembler =
				new AGALMiniAssembler();
			vertexShaderAssembler.assemble
				(
					Context3DProgramType.VERTEX,
					// 4x4 matrix multiply to get camera angle
					"m44 op, va0, vc0\n" +
					// tell fragment shader about XYZ
					"mov v0, va0\n" +
					// tell fragment shader about UV
					"mov v1, va1\n" +
					// tell fragment shader about RGBA
					"mov v2, va2"
				);
			// textured using UV coordinates
			var fragmentShaderAssembler1:AGALMiniAssembler
			= new AGALMiniAssembler();
			fragmentShaderAssembler1.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					// and store the interpolated value in ft0
					"tex ft0, v1, fs0 <2d,linear,repeat,miplinear>\n"+
					// move this value to the output color
					"mov oc, ft0\n"
				);
			// combine shaders into a program which we then upload to the GPU
			shaderProgram1 = context3D.createProgram();
			shaderProgram1.upload(
				vertexShaderAssembler.agalcode,
				fragmentShaderAssembler1.agalcode);
		}
		
		private function onContext3DCreate(event:Event):void
		{
			// Remove existing frame handler. Note that a context
			// loss can occur at any time which will force you
			// to recreate all objects we create here.
			// A context loss occurs for instance if you hit
			// CTRL-ALT-DELETE on Windows.
			// It takes a while before a new context is available
			// hence removing the enterFrame handler is important!
			//removeEventListener(Event.ENTER_FRAME,enterFrame);
			// Obtain the current context
			var t:Stage3D = event.target as Stage3D;
			context3D = t.context3D;
			if (context3D == null)
			{
				// Currently no 3d context is available (error!)
				trace('ERROR: no context3D - video driver problem?');
				return;
			}
			trace(context3D.driverInfo);
			// Disabling error checking will drastically improve performance.
			// If set to true, Flash sends helpful error messages regarding
			// AGAL compilation errors, uninitialized program constants, etc.
			context3D.enableErrorChecking = true;
			// The 3d back buffer size is in pixels (2=antialiased)
			if(stage.width == 0){
				context3D.configureBackBuffer(1000, 600, 2, true);
			}else{
				context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, true);
			}
			// assemble all the shaders we need
			initShaders();
			playerTexture = context3D.createTexture(playerTextureData.width, playerTextureData.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(playerTexture, playerTextureData.bitmapData);
			
			terrainTexture = context3D.createTexture(terrainTextureData.width, terrainTextureData.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(terrainTexture, terrainTextureData.bitmapData);
			
			cratersTexture = context3D.createTexture(cratersTextureData.width, cratersTextureData.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(cratersTexture, cratersTextureData.bitmapData);
			
			puffTexture = context3D.createTexture(puffTextureData.width, puffTextureData.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(puffTexture, puffTextureData.bitmapData);
			
			skyTexture = context3D.createTexture(skyTextureData.width, skyTextureData.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(skyTexture, skyTextureData.bitmapData);
			
			testTexture = context3D.createTexture(testTextureData.width, testTextureData.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(testTexture, testTextureData.bitmapData);
			
			// Initialize our mesh data - requires shaders and textures first
			initData();
			// create projection matrix for our 3D scene
			projectionmatrix.identity();
			// 45 degrees FOV, 640/480 aspect ratio, 0.1=near, 150000=far
			projectionmatrix.perspectiveFieldOfViewRH(
				45, stage.stageWidth / stage.stageHeight, 0.01, 150000.0);
			// start the render loop!
			this.dispatchEvent(new Event(Event.COMPLETE));
		}
		 
		private function initData():void
		{
			// create the camera entity
			trace("Creating the camera entity...");
			chase_camera = new Molehill_entity();
			// create the player model
			trace("Creating the player entity...");
			player = new Molehill_entity(my_obj_data5,context3D,shaderProgram1,playerTexture);
			// rotate to face forward
			player.rotationDegreesX = -90;
			//player.rotationDegreesZ = -90;
			player.z = 2100;
			
			trace("Parsing the terrain...");
			// add some terrain to the props list
			var terrain:Molehill_entity = new Molehill_entity(terrain_obj_data,context3D,shaderProgram1,terrainTexture);
			terrain.rotationDegreesZ = 90;
			terrain.y = -50;
			props.push(terrain);
			trace("Cloning the terrain...");
			
			// use the same mesh in another location
			var terrain2:Molehill_entity = terrain.clone();
			terrain2.z = -4000;
			props.push(terrain2);
			trace("Parsing the asteroid field...");
			
			// add an asteroid field to the props list
			asteroids1 = new Molehill_entity(asteroids_obj_data,context3D,shaderProgram1,cratersTexture);
			asteroids1.rotationDegreesZ = 90;
			asteroids1.scaleXYZ = 200;
			asteroids1.y = 500;
			asteroids1.z = -1000;
			props.push(asteroids1);
			trace("Cloning the asteroid field...");
			
			// use the same mesh in multiple locations
			asteroids2 = asteroids1.clone();
			asteroids2.z = -5000;
			props.push(asteroids2);
			
			asteroids3 = asteroids1.clone();
			asteroids3.z = -9000;
			props.push(asteroids3);
			
			asteroids4 = asteroids1.clone();
			asteroids4.z = -9000;
			asteroids4.y = -500;
			props.push(asteroids4);
			
			trace("Parsing the engine glow...");
			engine_glow = new Molehill_entity(puff_obj_data,context3D,shaderProgram1,puffTexture);
			// follow the player's ship
			engine_glow.follow(player);
			// draw as a transparent particle
			engine_glow.blend_src = Context3DBlendFactor.ONE;
			engine_glow.blend_dst = Context3DBlendFactor.ONE;
			engine_glow.depth_test = false;
			engine_glow.culling_mode = Context3DTriangleFace.NONE;
			engine_glow.y = -1.0;
			engine_glow.scaleXYZ = 0.5;
			particles.push(engine_glow);
			
			trace("Parsing the sky...");
			sky = new Molehill_entity(sky_obj_data,context3D,shaderProgram1,skyTexture);
			// follow the player's ship
			sky.follow(player);
			sky.depth_test = false;
			sky.depth_test_mode = Context3DCompareMode.LESS;
			sky.culling_mode = Context3DTriangleFace.NONE;
			sky.z = 2000.0;
			sky.scaleX = 40000;
			sky.scaleY = 40000;
			sky.scaleZ = 10000;
			sky.rotationDegreesX = 30;
			props.push(sky);
			
			trace("Creating an explosion particle system...");
			explo = new Molehill_particle(explosion1_data,context3D,testTexture,explosion2_data);
			// create a particle system
			particle_system = new Game_particlesystem;
			// define the types of particles
			particle_system.define_particle("explosion", explo);
			
		}
		
		public function render_scene():void
		{
			scene_polycount = 0;
			
			viewmatrix.identity();
			// look at the player
			viewmatrix.append(chase_camera.transform);
			viewmatrix.invert();
			// tilt down a little
			viewmatrix.appendRotation(15, Vector3D.X_AXIS);
			// if mouselook is on:
			viewmatrix.appendRotation(gameinput.camera_angle_x,
				Vector3D.X_AXIS);
			viewmatrix.appendRotation(gameinput.camera_angle_y,
				Vector3D.Y_AXIS);
			viewmatrix.appendRotation(gameinput.camera_angle_z,
				Vector3D.Z_AXIS);
			// render the player mesh from the current camera angle
			player.render(viewmatrix, projectionmatrix);
			scene_polycount += player.polycount;
			// loop through all known entities and render them
			for each (entity in props)
				entity.render(viewmatrix, projectionmatrix);
			for each (entity in enemies)
				entity.render(viewmatrix, projectionmatrix);
			for each (entity in bullets)
				entity.render(viewmatrix, projectionmatrix);
			for each (entity in particles)
				entity.render(viewmatrix, projectionmatrix);
				
			particle_system.render(viewmatrix, projectionmatrix);
			scene_polycount += particle_system.totalpolycount;
		}
		
		private function game_step(frame_ms:uint):void
		{
			// handle player input
			var move_amount:Number = move_speed * frame_ms;
			if (gameinput.pressing.up) 
				player.z -= move_amount;
			if (gameinput.pressing.down) 
				player.z += move_amount;
			if (gameinput.pressing.left) 
				player.x -= move_amount;
			if (gameinput.pressing.right) 
				player.x += move_amount;
			
			if (gameinput.pressing.fire)
			{
				if (gametimer.game_elapsed_time >= next_shoot_time)
				{
					//trace("Fire!");
					next_shoot_time = gametimer.game_elapsed_time + shoot_delay;
					// random location somewhere ahead of player
					var groundzero:Matrix3D = new Matrix3D;
					groundzero.prependTranslation(
						player.x + Math.random() * 200 - 100,
						player.y + Math.random() * 100 - 50,
						player.z + Math.random() * -1000 - 250);
					// create a new particle (or reuse an inactive one)
					particle_system.spawn("explosion", groundzero, 2000);
				}
			}
			
			//if (pressing.fire) // etc...
			// follow the player
			chase_camera.x = player.x;
			chase_camera.y = player.y + 1.5; // above
			chase_camera.z = player.z + 3; // behind
			
			// animate the asteroids
			asteroids1.rotationDegreesX += asteroid_rotation_speed * frame_ms;
			asteroids2.rotationDegreesX -= asteroid_rotation_speed * frame_ms;
			asteroids3.rotationDegreesX += asteroid_rotation_speed * frame_ms;
			asteroids4.rotationDegreesX -= asteroid_rotation_speed * frame_ms;
			
			// animate the engine glow - spin fast and pulsate slowly
			engine_glow.rotationDegreesZ += 10 * frame_ms;
			engine_glow.scaleXYZ = Math.cos(gametimer.game_elapsed_time/66) / 20 + 0.5;
			
			// advance all particles based on time
			particle_system.step(frame_ms);
		}
		
		// for efficiency, this function only runs occasionally
		// ideal for calculations that don't need to be run every frame
		private function heartbeat():void
		{
			return;
			trace('heartbeat at ' + gametimer.game_elapsed_time + 'ms');
			trace('player ' + player.pos_string());
			trace('camera ' + chase_camera.pos_string());
		}
		
		public function render():void{
			// clear scene before rendering is mandatory
			context3D.clear(0,0,0);
			// count frames, measure elapsed time
			gametimer.tick();
			// update all entities positions, etc
			game_step(gametimer.frame_ms);
			// render everything
			render_scene();
			// present/flip back buffer
			// now that all meshes have been drawn
			context3D.present();
			// update the FPS display
			fps_ticks++;
			var now:uint = getTimer();
			var delta:uint = now - fps_last;
			// only update the display once a second
			if (delta >= 1000)
			{
				var fps:Number = fps_ticks / delta * 1000;
				fps_tf.text = fps.toFixed(1) + " fps";
				fps_ticks = 0;
				fps_last = now;
			}
			// update the rest of the GUI
			updateScore();
		}
		
		
		
		/**end*/
	}
}